home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-04-22 | 25.2 KB | 658 lines | [TEXT/CCL2] |
- ;;;
- ;;; fred-keystroke-macros.lisp
- ;;;
- ;;; Matthew Cornell
- ;;; Amherst, MA
- ;;; cornell@cs.umass.edu
- ;;;
-
- #|
- ================================================================
- Purpose ========================================================
- ================================================================
- Defines functions and command bindings to do simple FRED keyboard macro
- recording.
-
- Copyright © 1990-92 Matthew Cornell. All Rights Reserved. Send
- bugs, comments, questions, and fixes to cornell@cs.umass.edu.
-
- Instructions:
- Just evaluate this buffer and use the "Macros" menu. It has three
- hierarchical menus that allow you to operate on any named macro, and below
- these three menus are these five items. To record a macro you start
- recording (with either the "Recording" menu item or with the keystroke
- bound to ed-start-kbd-macro), do your keystrokes, and end recording
- (in ways similar to starting). To use a macro you can run the last one
- recorded with the "Run New" menu item, run one that you named with the
- "Name Last" menu item, or run the last named macro you called, using the
- "Run Last" menu item.
-
- Each menu's functions are listed next.
-
-
- Run Runs a named macro.
- Insert Inserts a named macro's definition (which you can then edit and
- re-evaluate).
- Delete Deletes a named macro.
- ---------
- Recording Toggles recording. Has a checkmark during recording.
- Run New Runs the last recorded macro.
- Name New Attaches an asked-for name to the current last recorded macro and
- saves it. The new macro will show then appear on the
- hierarchical menus.
- ---------
- Run Last Runs the last named macro (not the last recorded macro).
- Insert All Runs the Insert function on all named macros. This function is
- useful for saving macros to files.
- Delete All Runs the Delete function on all named macros.
-
-
- ================================================================
- Status =========================================================
- ================================================================
- Implemented. Supports one level of recording but recursive macro
- invocations.
-
- Bugs:
- - Macros run recursively within call-named-macro are saved as the last kbd
- macro.
- Fix: Added :save-as-last? keyword to it.
- - Removing the keystroke(s) that called ed-end-kbd-macro: I can't get
- comtab-find-keys to find that function. How does ED-HELP do it?
- Fix: I'm using cheezy-find-all-keys to help.
- - Fred prefixes: the first keystroke-name is run n times then the
- others n times. Run-fred-command does this. How to turn off normal Fred
- prefix handling?
- Fix: Run-keystroke-name-list's :around method does an empty run using
- #'identity, which satiates run-fred-command's need to prefix.
- - From neves@ils.nwu.EDU on 09-Mar-92 :
- I have problems searching within a macro…
-
-
- Desires:
- - Call ed-end-kbd-macro cancel (c-g) is done and
- *keystroke-recorder-installed?* .
- - Mini-buffer feedback of recording in progress. Fred seems to update it
- without set-mini-buffer's help.
- - A way to record keystrokes that activate menus. MCL bypasses
- Fred keystroke processing to do this.
- - To record menu selections (tricky for hierarchical menus?) .
- - Should warn if recording over unamed macro?
-
-
- ================================================================
- Change history =================================================
- ================================================================
- 5-May-91 mc Created.
- 7-May-91 mc Finished (with plenty of Bill's help getting Fred dispatching
- to match the documentation).
- 8-May-91 mc Fixed first two bugs with work-arounds.
- 8-May-91 mc Added requires for the two patches.
- 10-May-91 mc Added run last feature.
- 13-May-91 mc Fixed last-macro bug in call-named-macro.
- 4-Aug-91 mc Released.
- 14-Mar-92 mc Fixed cheezy-find-all-keys and run-keystroke-name-list to use
- new shadowing comtab representation.
- Fixed run-keystroke-name-list :around to use
- fred-prefix-numeric-value, which was named prefix-numeric-value .
- 17-Apr-92 mc Fixed incremental search via Bill's code:
- Included body of i-search-patch.lisp .
- Redefined run-keystroke-name-list to bind
- ccl::*processing-events* .
- Fixed menu-update (eql *macro-menu*) to enable if a fred-mixin
- item is in the front.
- 22-Apr-92 mc Added call to fred-update in insert-all-kbd-macros (suggested
- by cartier@math.uqam.ca .
-
- |#
-
-
-
-
- ; i-search-patch.lisp
- ;
- ; i-search-do-keystroke no longer looks at *current-event*.
- ; This makes it possible to do keyboard macros that include c-s or c-r
-
- (in-package :ccl)
-
- (let ((*warn-if-redefine* nil)
- (*warn-if-redefine-kernel* nil))
-
- (defun i-search-do-keystroke (w)
- (declare (special *default-command-p*))
- (let* ((key-code *current-keystroke*)
- (char *current-character*))
- (if (and (or (%i> key-code 32)
- (eql char #\return)
- (eql char #\tab)
- (eql char #\space))
- (neq char #\rubout)
- (eql key-code (logand #xff key-code)))
- (i-search-add-char w char)
- (progn
- (ed-push-mark w (car *i-search-original-pos*))
- (remove-shadowing-comtab w)
- ;(collapse-selection w t) ; I like this but Mac weenies probably wont
- (run-fred-command w (keystroke-function w key-code))
- (setq *default-command-p* t)
- (i-search-all-done w)))))
-
- )
-
- (provide "I-SEARCH-PATCH")
-
-
-
-
- (in-package "COMMON-LISP-USER")
-
-
- ;;;================================================================
- ;;;Variables ======================================================
- ;;;================================================================
-
- (defvar *keystroke-recorder-installed?* nil
- "Non-nil when *old-fred-keystroke-hook* is set.")
-
- (defvar *old-fred-keystroke-hook* nil
- "The last value of *fred-keystroke-hook*.")
-
- (defvar *saved-keystroke-names* ()
- ;;
- ;; I use keystroke names (not codes) to simplify editing, viewing, and
- ;; saving.
- ;;
- "The stack of keystroke names or named-macro-names that
- save-and-call-current-keystroke and call-named-macro maintain.")
-
- (defvar *named-macros* ()
- "A list of named-macro objects. Maintained by name-and-save-last-macro")
-
- (defvar *last-called-macro* nil
- "The named-macro last called by call-named-macro.")
-
-
- ;;;================================================================
- ;;;named-macro ADT ================================================
- ;;;================================================================
-
- (defmethod make-named-macro ((name string) (keystroke-names list))
- "Returns a new named-macro object."
- ;;
- (cons name keystroke-names))
-
-
- (defmethod named-macro-name ((named-macro list))
- "Returns the name component of NAMED-MACRO."
- ;;
- (first named-macro))
-
-
- (defmethod named-macro-keystroke-names ((named-macro list))
- "Returns the keystroke-names component of NAMED-MACRO."
- ;;
- (rest named-macro))
-
-
- ;;;================================================================
- ;;;Top-level keyboard macro functions =============================
- ;;;================================================================
-
- (defun name-last-kbd-macro ()
- "Assign a name to the last keyboard macro defined."
- ;;
- (let* ((reversed-ks-names (reverse *saved-keystroke-names*))
- (length (length reversed-ks-names))
- (string (substitute #\¶ #\Return (format nil "~A" reversed-ks-names)))
- (too-long-length 20)
- (too-long? (> (length string) too-long-length))
- (name (get-string-from-user
- (format
- nil
- "Enter a name for the last recorded macro:~&~A item~P: ~A~A):"
- length length
- (if too-long? (subseq string 0 too-long-length) string)
- (if too-long? "…" "")))))
- (save-named-macro (make-named-macro name reversed-ks-names))))
-
-
- (defmethod insert-kbd-macro ((named-macro list) (stream stream))
- "Insert in buffer the definition of kbd macro NAMED-MACRO into WINDOW, as
- Lisp code."
- ;;
- (format stream "(save-named-macro (make-named-macro ~S '~S))"
- (named-macro-name named-macro)
- (named-macro-keystroke-names named-macro)))
-
-
- (defun insert-all-kbd-macros ()
- "Runs insert-kbd-macro on all named macros."
- ;;
- (let ((stream (front-window)))
- (cond (*named-macros*
- (format stream "~&;;; The current named macros:~&(progn~&")
- (dolist (named-macro *named-macros*)
- (insert-kbd-macro named-macro stream)
- (terpri stream))
- (princ ")" stream)
- (fred-update stream))
- (t
- (format t "~&No currently defined macros.")))))
-
-
- (defmethod delete-kbd-macro ((named-macro list))
- "Deletes the definition of kbd macro NAMED-MACRO and updates the macro menus."
- ;;
- (setf *named-macros* (delete named-macro *named-macros* :test #'equal))
- (update-macro-menus))
-
-
- (defun delete-all-kbd-macros ()
- "Runs delete-kbd-macro on all named macros."
- ;;
- (when (y-or-n-dialog (format nil "Delete all ~A macro~P?"
- (length *named-macros*) (length *named-macros*))
- :yes-text "Delete All")
- (map nil #'delete-kbd-macro *named-macros*)))
-
-
- (defmethod save-named-macro ((named-macro list))
- "Saves NAMED-MACRO on *named-macros* and updates the macro menus,
- replacing any macros with the same name."
- ;;
- ;; Remove those with the same names.
- ;;
- (setf *named-macros*
- (delete-if #'(lambda (current-named-macro)
- (string-equal (named-macro-name named-macro)
- (named-macro-name current-named-macro)))
- *named-macros*))
- (push named-macro *named-macros*)
- (update-macro-menus))
-
-
- (defmethod call-named-macro ((named-macro list) (window fred-mixin)
- &key (save-as-last? t))
- "Calls the first macro named by NAME passing it WINDOW, saves its name
- to *saved-keystroke-names* if *keystroke-recorder-installed?*, and sets
- *last-called-macro* to it. If SAVE-AS-LAST? is non-nil (the default) then
- sets *last-called-macro* to NAMED-MACRO."
- ;;
- (when save-as-last?
- (setf *last-called-macro* named-macro))
- (run-keystroke-name-list (named-macro-keystroke-names named-macro) window)
- (when *keystroke-recorder-installed?*
- (push (named-macro-name named-macro)
- *saved-keystroke-names*)))
-
-
- (defmethod run-keystroke-name-list :around
- ((keystroke-name-list list) (window fred-mixin))
- "An :around method that repeats the usual if there is a FRED prefix."
- ;;
- (let (;; Fred-prefix-argument is nil if no prefix, (number) if c-u
- ;; prefix used, and number otherwise.
- ;;
- (prefix-argument? (fred-prefix-argument window))
- (prefix-value (fred-prefix-numeric-value window)))
- ;;
- ;; Kludge: first do one empty Fred run to eliminate its automatic
- ;; prefixing if a prefix is present.
- ;;
- (when prefix-argument? (run-fred-command window #'identity))
- (dotimes (count (if prefix-argument?
- prefix-value ;prefix specified so use it
- 1)) ; no prefix so use default
- (call-next-method))))
-
-
- (defmethod run-keystroke-name-list ((keystroke-name-list list)
- (window fred-mixin))
- "Calls each function bound to keystroke name in KEYSTROKE-NAME-LIST in
- order, passing WINDOW to each. If any of KEYSTROKE-NAME-LIST's elements are
- strings that name named macros then that macro is called."
- ;;
- (let ((ccl::*processing-events* t)) ; don't look at user events until done
- (unwind-protect
- (run-keystroke-namelist-internal keystroke-name-list window)
- (ccl::remove-shadowing-comtab window))))
-
-
- (defmethod run-keystroke-namelist-internal ((keystroke-name-list list)
- (window fred-mixin))
- "Calls each function bound to keystroke name in KEYSTROKE-NAME-LIST in
- order, passing WINDOW to each. If any of KEYSTROKE-NAME-LIST's elements are
- strings that name named macros then that macro is called."
- ;;
- (dolist (keystroke-name keystroke-name-list)
- (cond
- ((stringp keystroke-name)
- ;;
- ;; KEYSTROKE-NAME might name a keyboard macro so call it if it does,
- ;; noting that this should not be saved as the last macro.
- ;;
- (let ((named-macro (find keystroke-name *named-macros*
- :test #'string-equal :key #'named-macro-name)))
- (when named-macro
- (call-named-macro named-macro window :save-as-last? nil))))
- (t
- ;;
- ;; KEYSTROKE-NAME doesn't name a macro so "run" the keystroke.
- ;;
- ;;
- ;; Following code is adapted from Bill's patch of 7-May-91.
- ;;
- (locally (declare (special ccl::*current-character*))
- (let* ((ccl::*current-keystroke* (keystroke-code keystroke-name))
- ;; The docs say bits 0-7 of the code are the character, so mask
- ;; the others off to get the character for
- ;; ccl::*current-character*.
- (keystroke-char
- (coerce (logand #x000000FF (keystroke-code keystroke-name))
- 'character))
- ;; This is the previous best way I could think of to get the
- ;; character from a keystroke-name or code:
- ;(keystroke-char (etypecase keystroke-name
- ; (list (first (remove-if-not #'characterp keystroke-name)))
- ; (character keystroke-name)))
- (ccl::*current-character* keystroke-char)
- (tab (or (fred-shadowing-comtab window)
- (slot-value window 'ccl::comtab))))
- (declare (special ccl::*current-keystroke* ccl::*processing-events*
- ccl::*current-character*))
- ;;
- (run-fred-command
- window (keystroke-function window ccl::*current-keystroke* tab))))))))
-
-
- ;;;================================================================
- ;;;Functions the Fred methods and key bindings use ================
- ;;;================================================================
-
- (defun save-and-call-current-keystroke (window-or-item)
- "The function bound to *fred-keystroke-hook* by ed-start-kbd-macro."
- ;;
- ;; Save the keystroke name bound to *current-keystroke* on
- ;; *saved-keystroke-names* then call its function.
- ;;
- (push (keystroke-name *current-keystroke*) *saved-keystroke-names*)
- (run-fred-command window-or-item
- (keystroke-function window-or-item *current-keystroke*)))
-
-
- (defun install-keystroke-recorder ()
- (unless *keystroke-recorder-installed?*
- (setf *keystroke-recorder-installed?* t
- *old-fred-keystroke-hook* *fred-keystroke-hook*
- *fred-keystroke-hook* #'save-and-call-current-keystroke)))
-
-
- (defun deinstall-keystroke-recorder ()
- (when *keystroke-recorder-installed?*
- (setf *fred-keystroke-hook* *old-fred-keystroke-hook*
- *old-fred-keystroke-hook* nil
- *keystroke-recorder-installed?* nil)))
-
-
- ;;;
- ;;; Make the minibuffer show when we're recording.
- ;;;
-
- #| ;;; This wasn't called when I thought it would be.
-
- (defmethod set-mini-buffer :around
- ((window-or-item fred-mixin) (string string) &rest format-args)
- "Precedes the usual with 'Macro: ' if *keystroke-recorder-installed?*."
- ;;
- (if *keystroke-recorder-installed?*
- (concatenate 'string "Macro: " (call-next-method))
- (call-next-method)))
- |#
-
-
- ;;;================================================================
- ;;;Fred methods and key bindings ==================================
- ;;;================================================================
-
- (defmethod ed-start-kbd-macro ((w fred-mixin))
- "Record subsequent keyboard input, defining a keyboard macro."
- ;;
- (cond (*keystroke-recorder-installed?*
- ;; Already recording so beep and set minibuffer.
- (set-mini-buffer w "Already recording.")
- (ed-beep))
- (t
- (setf *saved-keystroke-names* ())
- (install-keystroke-recorder)
- (set-mini-buffer w "Recording…"))))
-
-
- (defmethod cheezy-find-all-keys ((function-name symbol) (window fred-mixin))
- "Returns a list of keystroke name *sequences* bound to FUNCTION-NAME."
- ;;
- ;; I'm *sure* there is a better way…
- ;;
- (let ((shadowing-comtab (fred-shadowing-comtab window)) ;right?
- (keystroke-codes ()) ;a list of sequences of ks-codes
- l-comtab-keys)
- (when (and shadowing-comtab
- (setf l-comtab-keys (comtab-find-keys shadowing-comtab function-name)))
- (setf keystroke-codes (append (keystroke-name l-comtab-keys) keystroke-codes)))
- (when (setf l-comtab-keys (comtab-find-keys (slot-value window 'ccl::comtab)
- function-name))
- (setf keystroke-codes (append (keystroke-name l-comtab-keys)
- keystroke-codes)))
- (when (setf l-comtab-keys (comtab-find-keys *comtab* function-name))
- (setf keystroke-codes (append (keystroke-name l-comtab-keys) keystroke-codes)))
- (when (setf l-comtab-keys (comtab-find-keys *control-x-comtab* function-name))
- (dolist (code l-comtab-keys)
- (push (list '(:control #\x) (keystroke-name code))
- keystroke-codes)))
- ;;
- keystroke-codes))
-
-
- (defmethod ed-end-kbd-macro ((w fred-mixin) &key (from-menu? nil))
- "Finish defining a keyboard macro. If FROM-MENU? is non-nil then remove
- the last two "
- ;;
- (cond (*keystroke-recorder-installed?*
- (deinstall-keystroke-recorder)
- (set-mini-buffer w "Recording… Done.")
- ;;
- ;; Remove from *saved-keystroke-names* all keystrokes bound to
- ;; ed-end-kbd-macro.
- ;;
- (when (not from-menu?)
- (let* ((ks-list *saved-keystroke-names*)
- (ks-list-len (length ks-list))
- (rev-ks-list (reverse ks-list))
- subseq)
- (dolist (ks-name-seq (cheezy-find-all-keys 'ed-end-kbd-macro w))
- ;; Get the last |ks-name-seq| chars from rev-ks-list.
- (setf subseq
- (subseq rev-ks-list (- ks-list-len (length ks-name-seq))
- ks-list-len))
- (when (equal ks-name-seq subseq)
- ;; Ks-name-seq has a list of |ks-name-seq| kestroke names
- ;; that begins *saved-keystroke-names* so remove that many
- ;; from the front
- (dotimes (c (length ks-name-seq))
- (pop *saved-keystroke-names*))))))
- ;;
- ;; Old version:
- ;;
- ;; Remove (#\) (:CONTROL #\x) from *saved-keystroke-names* if
- ;; necessary. Problem: I don't know how many to remove and I can't
- ;; get comtab-find-keys to work. For now just blast away two and
- ;; hope!
- ;(when (and (not from-menu?)
- ; (>= (length *saved-keystroke-names*) 2))
- ; (pop *saved-keystroke-names*)
- ; (pop *saved-keystroke-names*))
- ;
- )
- (t
- ;; Not recording so beep and set minibuffer.
- (set-mini-buffer w "Not recording.")
- (ed-beep))))
-
-
- (defmethod ed-call-new-kbd-macro ((w fred-mixin))
- "Call the new keyboard macro that you defined with ed-start-kbd-macro."
- ;;
- (run-keystroke-name-list (reverse *saved-keystroke-names*) w))
-
-
- (defmethod ed-call-last-kbd-macro ((w fred-mixin))
- "Call the last keyboard macro that you ran with call-named-macro."
- ;;
- (cond (*last-called-macro*
- (call-named-macro *last-called-macro* w))
- (t
- (set-mini-buffer w "Must run a named macro first.")
- (ed-beep))))
-
-
- ;;; put the commands on standard (gemacs) keystrokes:
-
- (comtab-set-key *control-x-comtab* #\( 'ed-start-kbd-macro)
- (comtab-set-key *control-x-comtab* #\) 'ed-end-kbd-macro)
- (comtab-set-key *control-x-comtab* #\e 'ed-call-new-kbd-macro)
- (comtab-set-key *control-x-comtab* '(:control :meta #\e) 'ed-call-last-kbd-macro)
-
-
- ;;;================================================================
- ;;;Menu setup =====================================================
- ;;;================================================================
-
- ;;;
- ;;; The menu interface to macros allows running, listing, deleting, and
- ;;; inserting named-macro objects. It has a top-level menu named "Macros"
- ;;; with three hierarchial submenus that list all named macros: "Run",
- ;;; "Insert", and "Delete".
- ;;;
-
- ;;; The following three menus' contents are upated by update-macro-menus:
-
- (defvar *macro-run-menu* (make-instance 'menu :menu-title "Run"))
- (defvar *macro-insert-menu* (make-instance 'menu :menu-title "Insert"))
- (defvar *macro-delete-menu* (make-instance 'menu :menu-title "Delete"))
-
- (defvar *macro-menu* nil "A menu of useful Fred keyboard macro commands.")
-
- (defvar *record-menu-item* nil
- "An item whose string is either 'Record' or 'End recording' which provides a
- non-keyboard method of starting and stopping recording.")
-
-
- (defun install-macro-menu ()
- "Deinstalls the old 'Macros' menu and installs a new one."
- ;;
- (when (find-menu "Macros")
- (menu-deinstall (find-menu "Macros")))
- (setf *macro-menu*
- (make-instance
- 'menu :menu-title "Macros"
- :menu-items
- (list *macro-run-menu* *macro-insert-menu* *macro-delete-menu*
- (make-instance 'menu-item :menu-item-title "-") ;line
- (setf *record-menu-item*
- (make-instance 'menu-item
- :menu-item-title "Recording"
- :menu-item-action
- #'(lambda ()
- ;; Toggle recording according to
- ;; its current state.
- (if *keystroke-recorder-installed?*
- (ed-end-kbd-macro (front-window)
- :from-menu? t)
- (ed-start-kbd-macro (front-window))))))
- (make-instance 'menu-item
- :menu-item-title "Run New"
- :menu-item-action
- #'(lambda ()
- (ed-call-new-kbd-macro (front-window))))
- (make-instance 'menu-item
- :menu-item-title "Name New"
- :menu-item-action #'name-last-kbd-macro)
- (make-instance 'menu-item :menu-item-title "-") ;line
- (make-instance 'menu-item
- :menu-item-title "Run Last"
- :menu-item-action
- #'(lambda ()
- (ed-call-last-kbd-macro (front-window))))
- (make-instance 'menu-item
- :menu-item-title "Insert All"
- :menu-item-action #'insert-all-kbd-macros)
- (make-instance 'menu-item
- :menu-item-title "Delete All"
- :menu-item-action #'delete-all-kbd-macros))))
- ;;
- ;; Define the update functions.
- ;;
- (defmethod menu-update ((menu (eql *macro-menu*)))
- "Enables the *macro-run-menu* and *macro-insert-menu* submenus if the
- front window is typep 'fred-window or its current-key-handler is
- non-nil. Otherwise disables them."
- ;;
- (let ((front-window (front-window)))
- (cond ((or (typep front-window 'fred-window)
- (current-key-handler front-window))
- ;; A fred-window or fred-mixin so enable macros.
- (menu-enable *macro-run-menu*)
- (menu-enable *macro-insert-menu*))
- (t
- ;; Not a fred-window so disable macros.
- (menu-disable *macro-run-menu*)
- (menu-disable *macro-insert-menu*)))
- (call-next-method)))
-
-
- (defmethod menu-item-update ((menu-item (eql *record-menu-item*)))
- "Corrects *record-menu-item*'s checkmark according to whether
- *keystroke-recorder-installed?*."
- ;;
- (set-menu-item-check-mark
- menu-item (if *keystroke-recorder-installed?* t nil)))
-
- ;;
- ;; Install *macro-menu*.
- ;;
- (menu-install *macro-menu*))
-
-
- (install-macro-menu)
-
-
- (defun update-macro-menus ()
- "Updates *macro-run-menu*, *macro-insert-menu*, and *macro-delete-menu*
- to list all named macros."
- ;;
- ;; Remove all old menu-items.
- ;;
- (apply #'remove-menu-items *macro-run-menu* (menu-items *macro-run-menu*))
- (apply #'remove-menu-items *macro-insert-menu* (menu-items *macro-insert-menu*))
- (apply #'remove-menu-items *macro-delete-menu* (menu-items *macro-delete-menu*))
- ;;
- (dolist (named-macro (sort (copy-list *named-macros*)
- #'string< :key #'named-macro-name))
- (add-menu-items
- *macro-run-menu*
- (make-instance 'menu-item :menu-item-title (named-macro-name named-macro)
- :menu-item-action
- (let ((named-macro named-macro))
- #'(lambda () (call-named-macro named-macro (front-window))))))
- (add-menu-items
- *macro-insert-menu*
- (make-instance 'menu-item :menu-item-title (named-macro-name named-macro)
- :menu-item-action
- (let ((named-macro named-macro))
- #'(lambda () (insert-kbd-macro named-macro (front-window))))))
- (add-menu-items
- *macro-delete-menu*
- (make-instance 'menu-item :menu-item-title (named-macro-name named-macro)
- :menu-item-action
- (let ((named-macro named-macro))
- #'(lambda () (delete-kbd-macro named-macro)))))))